
import java.awt.Point;
import java.util.ArrayList;

public class ModelPanelGame extends AbstractModel {
	
	private Map map;
	private Point clickedBlockCoordLeft;
	private Point clickedBlockCorrdRight;
	private Point mouseCoord;
	private ArrayList<Point> reachableArea;
	private ArrayList<Point> attackableArea;
	private ArrayList<Point> path;
	private PathFinder pathFinder;
	private Integer round;
	
	/*
	 * Constructor
	 */
	public ModelPanelGame() {
		// Do nothing
	}
	
	/*
	 * Scan through the unit list and return the unit if it's selected
	 */
	private Unit getSelectedUnit() {
		for(int i = 0; i < map.getUnitsList().size(); i++) {
			if(map.getUnitsList().get(i).isSelected == true) {
				return map.getUnitsList().get(i);
			}
		}
		return null;
	}
	
	/*
	 * Scan throught the unit list and return the unit if it's coordination is same with the given one 
	 */
	private Unit getUnitByCoord(Point coord) {
		for(int i = 0; i < map.getUnitsList().size(); i++) {
			if(map.getUnitsList().get(i).getUnitCoord().equals(coord) == true) {
				return map.getUnitsList().get(i);
			}
		}
		return null;
	}
	
	/*
	 * Mutators
	 */	
	public void setMap(Map m) {
		System.out.println(this.getClass().toString() + ".setMap() called");
		Map oldMap;
		if(map == null)
			oldMap = new Map();
		else
			oldMap = map;
		map = new Map(m);
		firePropertyChange("map", oldMap, map);
		
		/*
		 * Pass the map to pathFinder
		 */
		if(pathFinder == null)
			pathFinder = new PathFinder();
		pathFinder.setMap(map);
	}
	
	public void setClickedBlockCoordLeft(Point cBCL) {
		System.out.println(this.getClass().toString() + ".setClickedBlockCoordLeft() called");
		Point oldClickedBlockCoordLeft;
		if(clickedBlockCoordLeft == null)
			oldClickedBlockCoordLeft = new Point();
		else
			oldClickedBlockCoordLeft = clickedBlockCoordLeft;
		clickedBlockCoordLeft = cBCL;
		firePropertyChange("clickedBlockCoordLeft", oldClickedBlockCoordLeft, clickedBlockCoordLeft);
		
		/*
		 * Check if a unit is clicked
		 * Conditions:
		 * 1. A reachable area is NOT available (null || empty)
		 * 2. An attackable area is NOT available (null || empty)
		 */
		if((reachableArea == null || reachableArea.equals(new ArrayList<Point>())) &&
				(attackableArea == null || attackableArea.equals(new ArrayList<Point>()))) {
			for(int i = 0; i < map.getUnitsList().size(); i++) {
				if(clickedBlockCoordLeft.equals(map.getUnitsList().get(i).getUnitCoord())) {
					setUnitSelected(new Point());	// Set all units unselected
					setUnitSelected(clickedBlockCoordLeft);	// Unit.isSelected -> true
					return;
				}
			}
		}
		
		/*
		 * Check if a destination block is clicked
		 * Conditions:
		 * 1. A reachable area is available (NOT null && NOT empty)
		 * 2. An attackable area is NOT available (null || empty) 
		 */
		if(reachableArea != null && !reachableArea.equals(new ArrayList<Point>()) &&
				(attackableArea == null || attackableArea.equals(new ArrayList<Point>()))) {
			for(int i = 0; i < reachableArea.size(); i++) {
				if(clickedBlockCoordLeft.equals(reachableArea.get(i))) {	// Check if mouse clicked inside the reachable area
					firePropertyChange("isDestSet", false, true);	// Tell the destination has been set
					setUnitCoord(getSelectedUnit().getUnitCoord(), clickedBlockCoordLeft);	// Assign the new coordination to the selected unit
					setReachableArea(new ArrayList<Point>());	// Clear reachable area
					setPath(new ArrayList<Point>());	// Clear path
					System.out.println(getSelectedUnit().getUnitCoord().x);
					setAttackableArea(getSelectedUnit().getAttackableArea());	// Set attackable area
					return;
				}
			}
		}
		
		/*
		 * Check if an enemy unit is clicked to attack or just rest
		 * Conditions:
		 * 1. A reachable area is NOT available (null || empty)
		 * 2. An attackable area is available (NOT null && NOT empty) 
		 */
		if((reachableArea == null || reachableArea.equals(new ArrayList<Point>())) &&
				attackableArea != null && !attackableArea.equals(new ArrayList<Point>())) {
			for(int i = 0; i < attackableArea.size(); i++) {
				if(clickedBlockCoordLeft.equals(attackableArea.get(i))) {	// Check if mouse clicked inside the attackable area, attack
					
					new Attack(getSelectedUnit(), getUnitByCoord(clickedBlockCoordLeft));	// HeZHANG - launch attack
					setAttackedUnitCoord(clickedBlockCoordLeft);	// Tell view the attack has been launched
					if(getUnitByCoord(clickedBlockCoordLeft).isDestroyed()) {	// Check if the unit is destroyed
						setDestroyedUnitCoord(clickedBlockCoordLeft);	// Tell view the unit is destroyed so as to display the animation
						map.getUnitsList().remove(getUnitByCoord(clickedBlockCoordLeft));	// Remove the unit from the list
						firePropertyChange("unit", new Unit(), map.getUnitsList().get(i));
					}
					
					getSelectedUnit().setIsActive(false);
					setAttackableArea(new ArrayList<Point>()); // Clear attackable area
					setUnitSelected(new Point());	// // Set all units unselected
				}
				else if(clickedBlockCoordLeft.equals(getSelectedUnit().getUnitCoord())) {	// Check if mouse clicked right on the selected unit, rest
					getSelectedUnit().setIsActive(false);
					setAttackableArea(new ArrayList<Point>()); // Clear attackable area
					setUnitSelected(new Point());	// // Set all units unselected
				}
			}
		}
	}
	
	public void setClickedBlockCoordRight(Point cBCR) {
		clickedBlockCorrdRight = cBCR;
		
		if(attackableArea != null && !attackableArea.equals(new ArrayList<Point>()))	// De-select is not allowed without choosing a target to attack or just resting
			return;
		
		setUnitSelected(new Point());
		setReachableArea(new ArrayList<Point>());
		setPath(new ArrayList<Point>());
	}
	
	/*
	 * This method is called when the mouse is moving inside the game panel
	 */
	public void setMouseCoord(Point mC) {
		System.out.println(this.getClass().toString() + ".setMouseCoord() called");
		mouseCoord = mC;
		
		/*
		 * Check if the mouse is moving inside the reachable area
		 * Conditions:
		 * 1. A reachable area is available (NOT null && NOT empty)
		 */
		if(reachableArea != null && !reachableArea.equals(new ArrayList<Point>())) {
			if(reachableArea.contains(mC)) {
				pathFinder.calcPath(mC);
				setPath(pathFinder.getPath());
			}
		}
	}
	
	/*
	 * Set the unit, specified by the given point, selected (say, changing the isSelected flag)
	 * If the given point is new Point(), just unselect all units
	 */
	public void setUnitSelected(Point sUC) {
		System.out.println(this.getClass().toString() + ".setUnitSelected() called");
		
		/*
		 * Unselect selected unit
		 */
		if(sUC.equals(new Point())) {
			for(int i = 0; i < map.getUnitsList().size(); i++) {
				if(map.getUnitsList().get(i).isSelected) {
					Unit oldSelectedUnit = new Unit(map.getUnitsList().get(i));
					map.getUnitsList().get(i).setIsSelected(false);
					firePropertyChange("unit", oldSelectedUnit, new Unit(map.getUnitsList().get(i)));
					return;
				}
			}
			return;
		}
		
		/*
		 * Set a unit selected
		 */
		for(int i = 0; i < map.getUnitsList().size(); i++) {
			if(map.getUnitsList().get(i).getUnitCoord().equals(sUC)) {
				Unit oldUnselectedUnit = map.getUnitsList().get(i);
				map.getUnitsList().get(i).setIsSelected(true);
				firePropertyChange("unit", oldUnselectedUnit, new Unit(map.getUnitsList().get(i)));
				break;
			}
		}
		
		/*
		 * Call pathFinder to construct reachable area if the selected unit is active
		 */
		if(getSelectedUnit().isActive()) {
			if(pathFinder == null)
				pathFinder = new PathFinder();
			pathFinder.setMap(map);
			pathFinder.setSelectedUnitCoord(getSelectedUnit().getUnitCoord());
			pathFinder.calcReachableArea();
			setReachableArea(pathFinder.getReachableArea());
		}
	}
	
	public void setUnitCoord(Point oldCoord, Point newCoord) {
		System.out.println(this.getClass().toString() + ".setUnitCoord() called");	
		for(int i = 0; i < map.getUnitsList().size(); i++) {
			if(map.getUnitsList().get(i).getUnitCoord().equals(oldCoord)) {
				Unit oldUnit = new Unit(map.getUnitsList().get(i));
				map.getUnitsList().get(i).setUnitCoord(newCoord);
				firePropertyChange("unit", oldUnit, new Unit(map.getUnitsList().get(i)));
			}
		}
	}
	
	public void setReachableArea(ArrayList<Point> rA) {
		System.out.println(this.getClass().toString() + ".setReachableArea() called");
		ArrayList<Point> oldReachableArea;
		if(reachableArea == null)
			oldReachableArea = new ArrayList<Point>();
		else
			oldReachableArea = reachableArea;
		reachableArea = rA;
		firePropertyChange("reachableArea", oldReachableArea, reachableArea);
	}
	
	public void setAttackableArea(ArrayList<Point> aA) {
		System.out.println(this.getClass().toString() + ".setAttackableArea() called");
		ArrayList<Point> oldAttackableArea;
		if(attackableArea == null)
			oldAttackableArea = new ArrayList<Point>();
		else
			oldAttackableArea = attackableArea;
		attackableArea = aA;
		firePropertyChange("attackableArea", oldAttackableArea, attackableArea);
	}
	
	public void setPath(ArrayList<Point> p) {
		System.out.println(this.getClass().toString() + ".setPath() called");
		ArrayList<Point> oldPath;
		if(path == null)
			oldPath = new ArrayList<Point>();
		else
			oldPath = path;
		path = p;
		firePropertyChange("path", oldPath, path);
	}
	
	/*
	 * If the given parameter is 1, round++.
	 * If 0, set round = 0.
	 */
	public void setRound(Integer r) {
		System.out.println(this.getClass().toString() + ".setRound() called");
		
		setReachableArea(new ArrayList<Point>());	// Clear reachable area
		setAttackableArea(new ArrayList<Point>());	// Clear attackable area
		setUnitSelected(new Point());	// // Set all units unselected
		
		Integer oldRound = null;
		if(round == null) {
			oldRound = 0;
			round = 1;
		}
		else {
			if(r == 1) {
				oldRound = round;
				round += 1;
			}
			else if(r == 0) {
				oldRound = 0;
				round = 1;
			}
		}
		firePropertyChange("round", oldRound, new Integer(round));
		
		/*
		 * Set units active or inactive for next round
		 */
		for(int i = 0; i < map.getUnitsList().size(); i++) {
			if(round % 2 == 1) {
				Unit oldUnit = map.getUnitsList().get(i);
				if(map.getUnitsList().get(i).getSide() == 1)
					map.getUnitsList().get(i).setIsActive(true);
				else
					map.getUnitsList().get(i).setIsActive(false);
				firePropertyChange("unit", oldUnit, new Unit(map.getUnitsList().get(i)));
			}
			else if(round % 2 == 0) {
				Unit oldUnit = map.getUnitsList().get(i);
				if(map.getUnitsList().get(i).getSide() == 2)
					map.getUnitsList().get(i).setIsActive(true);
				else
					map.getUnitsList().get(i).setIsActive(false);
				firePropertyChange("unit", oldUnit, new Unit(map.getUnitsList().get(i)));
			}
		}
	}
	
	public void setAttackedUnitCoord(Point aUC) {
		firePropertyChange("attackedUnitCoord", new Point(), aUC);
	}
	
	public void setDestroyedUnitCoord(Point dUC) {
		map.getUnitsList().remove(getUnitByCoord(dUC));
		firePropertyChange("destroyedUnitCoord", new Point(), dUC);
	}
	
	/*
	 * Accessors
	 */
	public Map getMap() {
		return map;
	}
}
